package com.gitee.fastmybatis.core.mapper;

import com.gitee.fastmybatis.core.FastmybatisContext;
import com.gitee.fastmybatis.core.MybatisContext;
import com.gitee.fastmybatis.core.query.LambdaQuery;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.fastmybatis.core.query.UpdateQuery;
import com.gitee.fastmybatis.core.support.ColumnValue;
import com.gitee.fastmybatis.core.support.Getter;
import com.gitee.fastmybatis.core.update.ModifyAttrsRecordProxyFactory;
import com.gitee.fastmybatis.core.update.UpdateWrapper;
import com.gitee.fastmybatis.core.util.ClassUtil;
import org.apache.ibatis.annotations.Param;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;


/**
 * 具备更新功能的Mapper
 *
 * @param <E> 实体类
 * @author tanghc
 */
public interface UpdateMapper<E> extends Mapper<E> {
    /**
     * 更新，更新所有字段
     *
     * @param entity 实体类
     * @return 受影响行数
     */
    int update(E entity);

    /**
     * 更新，忽略null字段
     *
     * @param entity 实体类
     * @return 受影响行数
     */
    int updateIgnoreNull(E entity);

    /**
     * 根据条件更新<br>
     * 当使用<code>Entitys.of(TUser.class, id);</code>时，可以更新null值
     * <pre>
     * {@literal
     * Query query = new Query().eq("state", 2);
     * TUser user = new TUser();
     * user.setUsername("李四");
     * int i = mapper.updateByQuery(user, query);
     * }
     * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
     *
     * // 更新null值
     * {@literal
     * TUser user = Entitys.of(TUser.class);
     * user.setUsername(null);
     * user.setAddTime(new Date());
     * // 批量更新 user_name和add_time
     * Query query = Query.create().eq("username", "张三");
     * int i = mapper.updateByQuery(user, query);
     * }
     * 对应SQL: UPDATE `t_user` SET `username`=null, add_time=? WHERE username = ?
     * </pre>
     *
     * @param entity           待更新的数据
     * @param query            更新条件
     * @param ignoreProperties 指定忽略更新的字段，可以是实体类属性名，也可以是数据库字段名
     * @return 受影响行数
     */
    default int updateByQuery(@Param("entity") E entity, @Param("query") Query query, @Param("ignoreProperties") String... ignoreProperties) {
        Objects.requireNonNull(entity, "entity can not null");
        List<String> ignorePropertieList = new ArrayList<>(Arrays.asList(ignoreProperties));
        if (entity instanceof UpdateWrapper) {
            // 开启强制更新，那么null字段也会更新进去
            query.enableForceUpdate();
            UpdateWrapper uw = (UpdateWrapper) entity;
            Set<String> ignorePropertieSet = FastmybatisContext.getIgnoreProperties(ModifyAttrsRecordProxyFactory.getSrcClass(entity.getClass()), uw.getUpdates().keySet());
            ignorePropertieList.addAll(ignorePropertieSet);
        }
        return updateByQuery(entity, query, ignorePropertieList);
    }

    /**
     * 根据条件更新<br>
     * <pre>
     * {@literal
     * Query query = new Query().eq("state", 2);
     * TUser user = new TUser();
     * user.setUsername("李四");
     * user.setAddTime(new Date());
     * // 忽略更新add_time字段
     * int i = mapper.updateByQuery(user, query, Arrays.asList("addTime"));
     * }
     * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
     * </pre>
     *
     * @param entity           待更新的数据
     * @param query            更新条件
     * @param ignoreProperties 指定忽略更新的字段，可以是实体类属性名，也可以是数据库字段名
     * @return 受影响行数
     */
    int updateByQuery(@Param("entity") E entity, @Param("query") Query query, @Param("ignoreProperties") Collection<String> ignoreProperties);

    /**
     * 根据条件强制更新,只要是调用setter赋值的属性都更新哪怕赋的值为null<br>
     * <pre>
     * {@literal
     * TUser user = Entitys.of(TUser.class, 123);
     * user.setUsername(null);
     * user.setAddTime(new Date());
     * // 强制更新 user_name和add_time
     * int i = mapper.forceUpdateById(user);
     * }
     * 对应SQL: UPDATE `t_user` SET `username`=null, add_time='2023-07-18 11:34:44' WHERE id = 123;
     * </pre>
     *
     * @param entity 待更新的数据
     * @return 受影响行数
     */
    default int forceUpdateById(E entity) {
        return forceUpdateById(entity, true);
    }

    /**
     * 根据条件强制更新,只要是调用setter赋值的属性都更新哪怕赋的值为null<br>
     * <pre>
     * {@literal
     * TUser user = Entitys.of(TUser.class, 123);
     * user.setUsername(null);
     * user.setAddTime(new Date());
     * // 强制更新 user_name和add_time
     * int i = mapper.forceUpdateById(user,true);
     * }
     * 对应SQL: UPDATE `t_user` SET `username`=null, add_time='2023-07-18 11:34:44' WHERE id = 123;
     * </pre>
     *
     * @param entity                  待更新的数据
     * @param ignoreLogicDeleteColumn 无视逻辑删除字段
     * @return 受影响行数
     */
    default int forceUpdateById(E entity, boolean ignoreLogicDeleteColumn) {
        Objects.requireNonNull(entity, "entity can not null");
        if (!(entity instanceof UpdateWrapper)) {
            throw new IllegalStateException("please wrapper entity with Entitys.of() before call setter!!");
        }

        String pkColumnName = FastmybatisContext.getPkColumnName(ModifyAttrsRecordProxyFactory.getSrcClass(entity.getClass()));
        Object pkValue = FastmybatisContext.getPkValue(entity);
        Query query = new Query().eq(pkColumnName, pkValue);
        if (ignoreLogicDeleteColumn) {
            query.ignoreLogicDeleteColumn();
        }

        return updateByQuery(entity, query);
    }


    /**
     * 根据条件更新，map中的数据转化成update语句set部分，key为数据库字段名<br>
     * <pre>
     * {@literal
     * Query query = new Query().eq("id", 1);
     * // key为数据库字段名
     * Map<String, Object> map = new LinkedHashMap<>();
     * map.put("username", "李四2");
     * map.put("remark", "123");
     * int i = mapper.updateByMap(map, query);
     * }
     * 对应SQL：UPDATE `t_user` SET username = ? , remark = ? WHERE id = ?
     * </pre>
     *
     * @param map   待更新的数据，key为数据库字段名
     * @param query 更新条件
     * @return 受影响行数
     * @see #update(UpdateQuery) 根据条件更新部分字段
     */
    int updateByMap(@Param("entity") Map<String, Object> map, @Param("query") Query query);

    /**
     * 根据条件更新部分字段
     * <pre>
     * {@literal
     * ColumnValue<TUser> columnValue = ColumnValue.create(TUser.class)
     *    .set(TUser::getUsername, "李四2")
     *    .set(TUser::getRemark, "123");
     * int i = mapper.updateByMap(columnValue, new Query().eq("id", 1));
     * }
     * </pre>
     *
     * @param columnValue 待更新的字段
     * @param query       更新条件
     * @return 返回影响行数
     * @see #update(LambdaQuery) 根据条件更新部分字段
     * @see #update(UpdateQuery) 根据条件更新部分字段
     */
    default int updateByQuery(ColumnValue<E> columnValue, Query query) {
        return this.updateByMap(columnValue.getValueMap(), query);
    }

    /**
     * 根据条件更新部分字段
     * <pre>
     * {@literal
     * UpdateQuery updateQuery = new UpdateQuery();
     * updateQuery.set("username", "王五");
     * updateQuery.eq("id", 6);
     * mapper.update(updateQuery);
     * }
     * </pre>
     * 对应SQL：UPDATE `t_user` SET username = ?  WHERE id = ?
     * @param updateQuery 更新条件
     * @return 返回影响行数
     */
    default int update(UpdateQuery updateQuery) {
        Map<String, Object> set = updateQuery.getSet();
        return updateByMap(set, updateQuery);
    }

    /**
     * 根据条件更新部分字段(Lambda)
     * <pre>
     * {@literal
     * LambdaQuery<TUser> updateQuery = Query.lambdaUpdate(TUser.class);
     * updateQuery.set(TUser::getUsername, "王五");
     * updateQuery.eq(TUser::getId, 6);
     * mapper.update(updateQuery);
     * }
     * 对应SQL：UPDATE `t_user` SET username = ?  WHERE id = ?
     * </pre>
     * @param updateQuery 更新条件
     * @return 返回影响行数
     */
    default int update(LambdaQuery<E> updateQuery) {
        Map<String, Object> set = updateQuery.getSet();
        return updateByMap(set, updateQuery);
    }

    /**
     * 根据主键id值更新部分字段
     * <pre>
     * {@literal
     * ColumnValue<TUser> columnValue = ColumnValue.create(TUser.class)
     *    .set(TUser::getUsername, "李四")
     *    .set(TUser::getRemark, "123");
     * int i = mapper.updateByMap(columnValue, 1);
     * }
     *
     * 对应SQL: UPDATE table SET username='李四', remark='123' WHERE id = 1;
     * </pre>
     *
     * @param columnValue 待更新的字段
     * @param id          主键id值
     * @return 返回影响行数
     */
    default int updateById(ColumnValue<E> columnValue, Serializable id) {
        Class<?> entityClass = MybatisContext.getEntityClassByMapperClass(this.getClass());
        String pkColumnName = FastmybatisContext.getPkColumnName(entityClass);
        return this.updateByMap(columnValue.getValueMap(), new Query().eq(pkColumnName, id));
    }

    /**
     * 根据某个字段值更新部分字段
     * <pre>
     * {@literal
     * ColumnValue<TUser> columnValue = ColumnValue.create(TUser.class)
     *    .set(TUser::getRemark, "123");
     * int i = mapper.updateByMap(columnValue, TUser::getUsername, "李四");
     * }
     *
     * 对应SQL: UPDATE table SET remark='123' WHERE username='李四';
     * </pre>
     *
     * @param columnValue 待更新的字段
     * @param getter      字段名：TUser::getId
     * @param value       字段值
     * @return 返回影响行数
     */
    default int updateByColumn(ColumnValue<E> columnValue, Getter<E, ?> getter, Object value) {
        String columnName = ClassUtil.getColumnName(getter);
        return this.updateByMap(columnValue.getValueMap(), new Query().eq(columnName, value));
    }

}
